EC2(AL2023)でヘッドレスChromeを動かしてみた
こんにちは、なおにしです。
検証のために静的サイトのコンテンツを扱っていた時、ふとJavaScriptの実行結果をコマンドで取得したくなりました。ヘッドレスブラウザを使用したことが無かったので、実際にインストールして使ってみるまでをまとめてみました。
やりたかったこと
例えば、以下のような簡単な応答を返す静的サイトがあったとします。
このサイトは以下のHTMLとJavaScriptの2ファイルで構成されています。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>アクセス情報</title>
</head>
<body>
<div id="access-info"></div>
<script src="access-info.js"></script>
</body>
</html>
document.addEventListener('DOMContentLoaded', function() {
const accessInfo = document.getElementById('access-info');
// アクセス日時
const dateInfo = `
<p>アクセス日時</p>
<p>${new Date().toLocaleString()}</p>
`;
// IPアドレス
fetch('https://api.ipify.org?format=json')
.then(response => response.json())
.then(data => {
const ipInfo = `
<p>IPアドレス</p>
<p>${data.ip}</p>
`;
accessInfo.innerHTML = dateInfo + ipInfo;
})
.catch(error => {
console.error('Error:', error);
accessInfo.innerHTML = dateInfo;
});
});
アクセス元IPアドレスをレスポンスとして返すために、JavaScript内で外部APIからデータを取得するようになっています。
コマンドで取得したいのはWebブラウザでアクセスした時の情報(アクセス日時とIPアドレス)ですが、例えばcurlコマンドを使用すると以下のようになります。
curlではJavaScriptを実行していないので、結果として上記のようにindex.html の内容が返ってくるのは当然です。
APIに対して直接リクエストを送信した結果を得るわけでもなく、今回のようにあくまでブラウザに表示された結果(JavaScriptがDOMを通じてHTMLを操作した結果)をCLIで取得するのであれば、ヘッドレスブラウザを使用する必要があります。
例えばGoogle Chromeでも、以下のとおり2017年からヘッドレスブラウザとしての動作を提供しています。
というわけで、実際にEC2のAmazon Linux 2023(AL2023) にChrome をインストールしてヘッドレスブラウザとしてデータを取得してみました。
やってみた
Amazon Linux 2(AL2) であれば EPEL リポジトリを追加してyumでChromiumをインストールするという流れですぐに試せそうでしたが、AL2023ではEPELをサポートしていないので別の方法を検討する必要がありました。
用途は異なりますが以下のサイトに同様の趣旨の記載とChrome のインストール手順があったため参考にします。
Amazon Linux などの一部の Linux ディストリビューションでは Chromium のインストールが困難です。
$ wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm
$ sudo dnf install ./google-chrome-stable_current_x86_64.rpm
google-chrome
コマンドで実行できるようになります。
前述のヘッドレスChromeに関するサイトに記載の内容を参考に、インストールしたChrome ヘッドレスブラウザとして実行してみます。
$ google-chrome --headless --disable-gpu --dump-dom https://example.com/
結果が取得できましたが、何やらエラーが出力されており、取得できた情報もcurlと同じです。
まず、エラーについてはいずれも「UPower」パッケージに関連しているもののようです。このパッケージはAL2には含まれていましたがAL2023には含まれていないパッケージの一つです。
AL2023はFedoraをベースとしたディストリビューションであるため、少しだけこちらからのパッケージインストールを試みましたが、依存関係やバージョン周りがまあまあ複雑だったため諦めました。。
ヘッドレスChrome自体は(エラー出力はあるものの)動いているため今回はエラー解消については割愛させていただきます。
というわけで実行結果がcurlと同じになってしまっていることを考えます。
ヘッドレスChromeの実行オプションを確認していくと、--virtual-time-budget
という項目がありました。説明内容は以下のとおりです。
--virtual-time-budget
は、時間に依存するコード (たとえば、setTimeout
/setInterval
) の「早送り」として機能します。これにより、ブラウザーはページのコードを可能な限り高速に実行するように強制され、その時間が実際に経過したとページに信じ込ませます。
実際にGUIのWebブラウザでサンプルのような静的サイトを開いたときも、JavaScriptが読み込まれるまでの時間がわずかながらも存在すると考えると、こちらのオプションを適用するのが良さそうです。
このため--virtual-time-budget
に100ミリ秒を設定して、ついでに標準エラー出力を捨てて実行してみます。
$ google-chrome --headless --disable-gpu --virtual-time-budget=100 --dump-dom https://example.com/ 2>/dev/null
想定どおり取得することができました。
なお、1ミリ秒を設定して何度か実行すると、--virtual-time-budget
を設定していないパターンと同じ結果が出力されることがありましたので、JavaScriptの実行に時間がかかる場合は調整が必要です。
補足
Mac で Google Chrome をインストールしている場合は、より簡単に同様の操作を確認可能です。
まず、chrome
でコマンドを実行できるようにするためにプログラム本体へのエイリアスを作成します。
$ alias chrome="/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome"
あとは同様にヘッドレスChromeとして実行します。
$ chrome --headless --disable-gpu --virtual-time-budget=100 --dump-dom https://example.com/
Macの環境ではUPower関連のエラーが出力されなかったため、標準エラー出力を捨てる処理は不要でした。
まとめ
スクレイピングなどでSeleniumを使う際にヘッドレスChrome(Chromium)と連携させるというように、コード内でヘッドレスブラウザを使用することの方が多いかとは思いますが、今回はcurlコマンドのように使うには?というイメージで確認してみました。
本記事がどなたかのお役に立てれば幸いです。